/*
 * Copyright oVirt Authors
 * SPDX-License-Identifier: Apache-2.0
*/

package org.ovirt.engine.api.v3;

import static org.ovirt.engine.api.v3.adapters.V3InAdapters.adaptIn;
import static org.ovirt.engine.api.v3.adapters.V3OutAdapters.adaptOut;

import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import org.ovirt.engine.api.model.Action;
import org.ovirt.engine.api.restapi.resource.BaseBackendResource;
import org.ovirt.engine.api.v3.types.V3Action;

/**
 * This is the base class for all the implementations of the V3 services. All these implementations have a
 * {@code delegate} field, that contains a reference to equivalent V4 service. The implementation will adapt the
 * input, send it to the V4 service, adapt the output and send it to the caller.
 */
public class V3Server<DELEGATE> {
    /**
     * The reference to the V4 implementation of the service.
     */
    private DELEGATE delegate;

    /**
     * Creates a new V3 implementation.
     *
     * @param delegate the reference to the V4 implementation of the service.
     */
    public V3Server(DELEGATE delegate) {
        Objects.requireNonNull(delegate, "The delegate of a V3 server can't be null");
        this.delegate = delegate;
    }

    /**
     * The reference to the V4 implementation of the service.
     */
    protected DELEGATE getDelegate() {
        return delegate;
    }

    // We need to have the URI info object injected, as we need to manually inject into the V4 implementation of
    // the service.
    @Context
    public void setUriInfo(UriInfo uriInfo) {
        if (delegate instanceof BaseBackendResource) {
            BaseBackendResource resource = (BaseBackendResource) delegate;
            resource.setUriInfo(uriInfo);
        }
    }

    // We need to have the HTTP headers injected, as we need to manually inject into the V4 implementation of the
    // service.
    @Context
    public void setHttpHeaders(HttpHeaders httpHeaders) {
        if (delegate instanceof BaseBackendResource) {
            BaseBackendResource resource = (BaseBackendResource) delegate;
            resource.setHttpHeaders(httpHeaders);
        }
    }

    /**
     * Converts a web application exception generated by V4 of the API into the equivalent V3 exception.
     */
    public static WebApplicationException adaptException(WebApplicationException exception) {
        return new WebApplicationException(adaptResponse(exception.getResponse()));
    }

    /**
     * Converts a response generated by V4 of the API into the equivalent V3 response.
     *
     * @param response the response generated by V4 of the API
     * @return the V3 equivalent response
     */
    public static Response adaptResponse(Response response) {
        if (response == null) {
            return null;
        }
        Object entity = response.getEntity();
        if (entity == null) {
            return response;
        }
        entity = adaptOut(entity);
        return Response.fromResponse(response).entity(entity).build();
    }

    /**
     * Calls a V4 method that generates a response, and adapts the result to V3, including the exceptions that may
     * be thrown by the method.
     *
     * @param method the method to call
     * @return the V3 equivalent response
     */
    public static Response adaptResponse(Supplier<Response> method) {
        try {
            return adaptResponse(method.get());
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }

    /**
     * Calls a V4 {@code get} method and adapts the result to V3, including the exceptions that may be thrown by the
     * method.
     *
     * @param method the {@code get} method to call
     * @return the V3 equivalent response
     */
    public static <V3, V4> V3 adaptGet(Supplier<V4> method) {
        try {
            return adaptOut(method.get());
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }

    /**
     * Calls a V4 {@code list} method and adapts the result to V3, including the exceptions that may be thrown by the
     * method.
     *
     * @param method the {@code list} method to call
     * @return the V3 equivalent response
     */
    public static <V3, V4> V3 adaptList(Supplier<V4> method) {
        try {
            return adaptOut(method.get());
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }

    /**
     * Adapts the given {@code object} to V4 of the API, then calls a V4 {@code add} method to add it, and adapts the
     * result to V3, including the exceptions that may be thrown by the method.
     *
     * @param method the {@code add} method to call
     * @param object the object to pass to the method as parameter
     * @return the V3 equivalent response
     */
    public static <V3, V4> Response adaptAdd(Function<V4, Response> method, V3 object) {
        try {
            return adaptResponse(method.apply(adaptIn(object)));
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }

    /**
     * Adapts the given {@code object} to V4 of the API, then calls a V4 {@code add} method to add it, and adapts the
     * result to V3, including the exceptions that may be thrown by the method.
     *
     * @param method the {@code update} method to call
     * @param object the object to pass to the method as parameter
     * @return the V3 equivalent response
     */
    public static <V3, V4> V3 adaptUpdate(Function<V4, V4> method, V3 object) {
        try {
            return adaptOut(method.apply(adaptIn(object)));
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }

    /**
     * Calls a V4 {@code remove} method and adapts the result to V3, including the exceptions that may be thrown by the
     * method.
     *
     * @param method the {@code remove} method to call
     * @return the V3 equivalent response
     */
    public static Response adaptRemove(Supplier<Response> method) {
        try {
            return adaptResponse(method.get());
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }

    /**
     * Calls a V4 action method and adapts the result to V3, including the exceptions that may be thrown by the
     * method.
     *
     * @param method the action method to call
     * @param action the action to pass to the action method as input parameter
     * @return the V3 equivalent response
     */
    public static Response adaptAction(Function<Action, Response> method, V3Action action) {
        try {
            return adaptResponse(method.apply(adaptIn(action)));
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }
}
